package org.veloxbot.api.methods;

import org.veloxbot.api.utils.Random;
import org.veloxbot.api.utils.ColorUtil;

import java.awt.Color;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.GameCanvas;

import java.awt.event.KeyEvent;

import java.awt.image.BufferedImage;

import java.util.List;
import java.util.ArrayList;
import java.util.Collections;

/**
 * @author Velox
 */
public class Game {

	public static final Rectangle BOUNDS = new Rectangle(0, 0, 767, 610);

	/**
	 * Gets the game's image.
	 *
	 * @return The game's image.
	 */
	public static BufferedImage getImage() {
		return GameCanvas.image;
	}

	/**
	 * Checks whether a point is on the screen or not.
	 *
	 * @param x An X location on the screen.
	 * @param y A Y location on the screen.
	 * @return Whether the point is on the screen or not.
	 */
	public static boolean isPointValid(final int x, final int y) {
		return !(x < 0 || x > BOUNDS.width || y < 0 || y > BOUNDS.height);
	}

	/**
	 * Checks whether a point is on the screen or not.
	 *
	 * @param point A point on the screen.
	 * @return Wehter the point is on the screen or not.
	 */
	public static boolean isPointValid(final Point point) {
		return point != null && isPointValid(point.x, point.y);
	}

	/**
	 * Gets the color of a point.
	 *
	 * @param x An X location on the screen.
	 * @param y A Y location on the screen.
	 * @return The color of the point, if on screen, otherwise Color.BLACK.
	 */
	public static Color getColorAt(final int x, final int y) {
		return isPointValid(x, y) ? new Color(getImage().getRGB(x, y)) : Color.BLACK;
	}

	/**
	 * Gets the color of a point.
	 *
	 * @param point A point on the screen.
	 * @return The color of the point, if on screen, otherwise Color.BLACK.
	 */
	public static Color getColorAt(final Point point) {
		return point != null ? getColorAt(point.x, point.y) : Color.BLACK;
	}

	/**
	 * Gets an array of points within the given bounds relative to the given parameters.
	 *
	 * @param bounds    The bounds to search in.
	 * @param color     The color to look for.
	 * @param tolerance The tolerance of the color to look for.
	 * @return An array of matched points within the given bounds.
	 */
	public static Point[] getPointsWithColor(final Rectangle bounds, final Color color, final int tolerance) {
		final List<Point> points = new ArrayList<Point>();
		for (int x = bounds.x; x < bounds.x + bounds.width; x++) {
			for (int y = bounds.y; y < bounds.y + bounds.height; y++) {
				final Color c = getColorAt(x, y);
				if (ColorUtil.dist(c, color) <= tolerance) {
					points.add(new Point(x, y));
				}
			}
		}
		return points.toArray(new Point[points.size()]);
	}

	/**
	 * Gets an array of points relative to the given parameters.
	 *
	 * @param color The color to look for.
	 * @return An array of matched points.
	 */
	public static Point[] getPointsWithColor(final Rectangle bounds, final Color color) {
		return getPointsWithColor(bounds, color, 0);
	}

	/**
	 * Gets an array of points relative to the given parameters.
	 *
	 * @param color     The color to look for.
	 * @param tolerance The tolerance of the color to look for.
	 * @return An array of matched points.
	 */
	public static Point[] getPointsWithColor(final Color color, final int tolerance) {
		return getPointsWithColor(BOUNDS, color, tolerance);
	}

	/**
	 * Gets an array of points relative to the given parameter.
	 *
	 * @param color The color to look for.
	 * @return An array of matched points.
	 */
	public static Point[] getPointsWithColor(final Color color) {
		return getPointsWithColor(color, 0);
	}

	/**
	 * Gets an array of points within the given bounds relative to the given parameter(s).
	 *
	 * @param bounds    The bounds to search in.
	 * @param tolerance The tolerance of the colors to look for.
	 * @param colors    The color(s) to look for.
	 * @return An array of matched points.
	 */
	public static Point[] getPointsWithColors(final Rectangle bounds, final int tolerance, final Color... colors) {
		final List<Point> points = new ArrayList<Point>();
		for (final Color color : colors) {
			Collections.addAll(points, getPointsWithColor(bounds, color, tolerance));
		}
		return points.toArray(new Point[points.size()]);
	}

	/**
	 * Gets an array of points relative to the given parameter(s).
	 *
	 * @param tolerance The tolerance of the colors to look for.
	 * @param colors    The color(s) to look for.
	 * @return An array of matched points.
	 */
	public static Point[] getPointsWithColors(final int tolerance, final Color... colors) {
		return getPointsWithColors(BOUNDS, tolerance, colors);
	}

	/**
	 * Gets an array of points relative to the given parameter(s)
	 *
	 * @param colors The color(s) to look for.
	 * @return An array of matched points.
	 */
	public static Point[] getPointsWithColors(final Color... colors) {
		return getPointsWithColors(0, colors);
	}

	/**
	 * An enum of game tabs.
	 */
	public static enum Tab {
		COMBAT(new Rectangle(522, 218, 30, 37), KeyEvent.VK_F5),
		SKILLS(new Rectangle(582, 218, 30, 37), -1),
		QUESTS(new Rectangle(612, 218, 30, 37), -1),
		TASKS(new Rectangle(552, 218, 30, 37), -1),
		INVENTORY(new Rectangle(642, 218, 30, 37), KeyEvent.VK_F1),
		EQUIPMENT(new Rectangle(672, 218, 30, 37), KeyEvent.VK_F2),
		PRAYER(new Rectangle(702, 218, 30, 37), KeyEvent.VK_F3),
		MAGIC(new Rectangle(732, 218, 30, 37), KeyEvent.VK_F4),
		SOF(new Rectangle(522, 516, 30, 37), -1),
		FRIENDS(new Rectangle(552, 516, 30, 37), -1),
		FRIENDS_CHAT(new Rectangle(582, 516, 30, 37), -1),
		CLAN_CHAT(new Rectangle(612, 516, 30, 37), -1),
		OPTIONS(new Rectangle(642, 516, 30, 37), -1),
		EMOTES(new Rectangle(672, 516, 30, 37), -1),
		MUSIC(new Rectangle(702, 516, 30, 37), -1),
		NOTES(new Rectangle(732, 516, 30, 37), -1);

		private final Rectangle bounds;
		private final int fkey;

		Tab(final Rectangle rectangle, final int fkey) {
			this.bounds = rectangle;
			this.fkey = fkey;
		}

		/**
		 * Gets the tab's bounds.
		 *
		 * @return
		 */
		public Rectangle getBounds() {
			return bounds;
		}

		/**
		 * Checks if the tab is open or not.
		 *
		 * @return <t>true</t> if the tab is open, otherwise <t>false</t>.
		 */
		public boolean isOpen() {
			return getPointsWithColor(bounds, new Color(254, 239, 114)).length > 0;
		}

		/**
		 * Opens the tab
		 *
		 * @param fkeys <t>true</t> to use fkeys, otherwise <t>false</t>.
		 * @return <t>true</t> if the tab was successfully opened, otherwise <t>false</t>.
		 */
		public boolean open(final boolean fkeys) {
			if (isOpen()) {
				return true;
			}
			if (fkeys && fkey != -1) {
				Keyboard.pressEventKey(fkey);
				return isOpen();
			} else {
				Mouse.click(getRandomPoint(), true);
				return isOpen();
			}
		}

		private Point getCenterPoint() {
			return new Point((int) bounds.getCenterX(), (int) bounds.getCenterY());
		}

		/**
		 * Gets a random point within the tab's bounds.
		 *
		 * @return A random point within the tab's bounds.
		 */
		public Point getRandomPoint() {
			return new Point(((int) bounds.getCenterX() + Random.nextInt(-bounds.width / 2, bounds.width / 2)), ((int) bounds.getCenterY() + Random.nextInt(-bounds.height / 2, bounds.height / 2)));
		}
	}

	/**
	 * Gets the currently opened tab.
	 *
	 * @return The currently opened tab.
	 */
	public static Tab getOpenTab() {
		for (final Tab tab : Tab.values()) {
			if (tab.isOpen()) {
				return tab;
			}
		}
		return null;
	}
}
